using Cairo;
using Gtk;
using Gdk;
using Gee;

namespace Ribbons {

	/** Ribbon widget. */
	public class Ribbon : Container {

		private const double BORDER_WIDTH = 2.0;
		private const double SPACE = 2.0;
		private const double PAGE_PADDING = 3.0;
		private const double TAB_PADDING = 4.0;
		private const double TABS_MIN_HPOS = 8.0;
		private const double LINE_WIDTH = 1.0;
		private const double ROUND_SIZE = 4.0;

		// XXX-GEETK: never used
//		private ColorScheme _color_scheme = new ColorScheme ();

		private Gee.List<RibbonPage> _pages;

		private Rectangle _body_allocation;
		private Rectangle _page_allocation;
		private Requisition _app_button_requisition;
		private Requisition _toolbar_requisition;
		private Requisition _shortcuts_requisition;
		private Requisition _page_requisition;

		private double _header_height;

		public signal void page_selected (RibbonPage page);
		public signal void page_added (RibbonPage page);
		public signal void page_moved (RibbonPage page);
		public signal void page_removed (RibbonPage page);

		private ApplicationButton _app_button;
		public ApplicationButton application_button {
			set {
				if (_app_button != null) {
					_app_button.unparent ();
				}
				_app_button = value;
				if (_app_button != null) {
					_app_button.visible = true;
					_app_button.set_parent (this);
				}
				show_all ();
			}
			get {
				return _app_button;
			}
		}

		private QuickAccessToolbar _toolbar;
		public QuickAccessToolbar quick_access_toolbar {
			set {
				if (_toolbar != null) {
					_toolbar.unparent ();
				}
				_toolbar = value;
				if (_toolbar != null) {
					_toolbar.visible = true;
					_toolbar.set_parent (this);
				}
				show_all ();
			}
			get {
				return _toolbar;
			}
		}

		/**
		 * Index of the currently selected page.
		 *
		 * Returns -1 if no page is selected.
		 */
		private int _current_page_index;
		public int current_page_index {
			set {
				if (_current_page_index != -1) {
					this.current_page.label.modify_fg (StateType.NORMAL,
							_theme.get_forecolor_for_ribbon_tabs (false));
					this.current_page.page.unparent ();
				}
				_current_page_index = value;
				if (_current_page_index != -1) {
					this.current_page.label.modify_fg (StateType.NORMAL,
							_theme.get_forecolor_for_ribbon_tabs (true));
					this.current_page.page.set_parent (this);
				}

				show_all ();
				queue_draw ();
			}
			get {
				return _current_page_index;
			}
		}

		/** Currently selected page. */
		public RibbonPage? current_page {
			owned get {						// XXX-GEETK: is this right?
				if (_current_page_index == -1) {
					return null;
				}
				return _pages[_current_page_index];
			}
		}

		/** Number of pages. */
		public int n_pages {
			get {
				return (int) _pages.size;
			}
		}

		/**
		 * Shortcuts widget.
		 *
		 * The shortcuts widget is displayed next to the tabs.
		 */
		private Widget _shortcuts;
		public Widget shortcuts {
			set {
				if (_shortcuts != null) {
					_shortcuts.unparent ();
				}
				_shortcuts = value;
				if (_shortcuts != null) {
					_shortcuts.visible = true;
					_shortcuts.set_parent (this);
				}
				queue_draw ();
			}
			get { return _shortcuts; }
		}

		/** Theme used to draw the widget. */
		private Theme _theme = new Theme ();
		public Theme theme {
			set {
				_theme = value;
				queue_draw ();
			}
			get { return _theme; }
		}

		/** Construction method. */
		construct {
			set_flags (get_flags () | WidgetFlags.NO_WINDOW);

			add_events (EventMask.BUTTON_PRESS_MASK
					| EventMask.BUTTON_RELEASE_MASK
					| EventMask.POINTER_MOTION_MASK);

			_pages = new ArrayList<RibbonPage> ();
			_current_page_index = -1;
		}

		/**
		 * Adds a new page after all existing pages.
		 *
		 * @param child   The widget to use as the content of the page.
		 * @param label   The widget to use as the tab.
		 */
		public void append_page (Widget child, Widget label) {
			insert_page (child, label, -1);
		}

		/**
		 * Adds a new page before all existing pages.
		 *
		 * @param child   The widget to use as the content of the page.
		 * @param label   The widget to use as the tab.
		 */
		public void prepend_page (Widget child, Widget label) {
			insert_page (child, label, 0);
		}

		/**
		 * Adds a new page at the specified position.
		 *
		 * @param child     The widget to use as the content of the page.
		 * @param label     The widget to use as the tab.
		 * @param position  The index (starting at 0) at which the page must be
		 *                  inserted, or -1 to insert the page after all
		 *                  existing pages.
		 */
		public void insert_page (Widget child, Widget label, int position) {
			var new_page = new RibbonPage (this, child, label);

			if (position == -1) {
				_pages.add (new_page);
			} else {
				_pages.insert (position, new_page);
					
				if (_current_page_index != -1) {
					if (position <= _current_page_index) {
						_current_page_index++;
					}
				}
			}

			if (_pages.size == 1) {
				this.current_page_index = 0;
			} else {
				label.modify_fg (StateType.NORMAL,
									_theme.get_forecolor_for_ribbon_tabs (false));
			}

			label.button_press_event.connect ((sender, event) => {
				select_ribbon_page (new_page);
			});

			label.enter_notify_event.connect ((sender, event) => {
				
			});

			label.leave_notify_event.connect ((sender, event) => {
				
			});

			page_added (new_page);
			for (int i = position + 1; i < _pages.size; i++) {
				page_selected (_pages[i]);
			}
		}

		/**
		 * Removes the specified page.
		 *
		 * @param page_number   Index of the page to remove.
		 */
		public void remove_page (int page_number) {
			if (_current_page_index != -1) {
				if (page_number < _current_page_index) {
					_current_page_index--;
				} else if (page_number == _current_page_index) {
					_current_page_index = -1;
				}
			}

			var page = _pages[page_number];
			if (_current_page_index == -1) {
				_pages.remove_at (_pages.size - 1);
			} else {
				_pages.remove_at (page_number);
			}

			page_removed (page);
		}

		/**
		 * Returns the index of the specified page given its content widget.
		 *
		 * @param child   The content of the page whose index must be returned.
		 * @return		  The index.
		 */
		public int page_num (Widget child) {
			// Since it is unlikely that the widget will contain more than
			// a dozen pages, it is just fine to do a linear search.
			for (int i = 0; i < _pages.size; i++) {
				if (_pages[i].page == child) {
					return i;
				}
			}
			return -1;
		}

		/**
		 * Returns the index of the specified page.
		 *
		 * @param page   The page whose index must be returned.
		 * @return       The RibbonPage.
		 */
		public int ribbon_page_num (RibbonPage page) {
			// Since it is unlikely that the widget will containe more than
			// a dozen pages, it is just fine to do a linear search.
			for (int i = 0; i < _pages.size; i++) {
				if (_pages[i] == page) {
					return i;
				}
			}
			return -1;
		}

		/**
		 * Sets the label widget of the specified page.
		 *
		 * @param page    The content of the page whose label must be modified.
		 * @param label   The new label widget.
		 */
		public void set_page_label (Widget child, Widget label) {
			_pages[page_num (child)].label = label;
		}

		/**
		 * Gets the label widget of the specified page.
		 *
		 * @param child   The content of the page whose label must be returned.
		 * @return        The label widget.
		 */
		public Widget get_page_label (Widget child) {
			return _pages[page_num (child)].label;
		}

		/**
		 * Returns the content widget of the n-th page.
		 *
		 * @param position   Index of the page whose content has to be returned.
		 * @return           The n-th page.
		 */
		public Widget get_nth_page (int position) {
			return _pages[position].page;
		}

		/**
		 * Returns the n-th page.
		 *
		 * @param position   Index of the page to return.
		 */
		public RibbonPage get_nth_ribbon_page (int position) {
			return _pages[position];
		}

		/**
		 * Selects the specified page.
		 *
		 * @param page   The page to select.
		 */
		public void select_ribbon_page (RibbonPage page) {
			int index = ribbon_page_num (page);
			if (index != -1) {
				this.current_page_index = index;
			}
			page_selected (page);
		}

		/** Selects the previous page. */
		public void prev_page () {
			int index = _current_page_index;
			if (index > 0) {
				this.current_page_index = index - 1;
			}
		}

		/** Selects the next page. */
		public void next_page () {
			int index = _current_page_index;
			if (index < this.n_pages - 1) {
				this.current_page_index = index + 1;
			}
		}

		protected override void forall_internal (bool include_internals,
		                                         Gtk.Callback callback)
		{
			if (is_shown (_toolbar)) {
				callback (_toolbar);
			}

			if (is_shown (_app_button)) {
				callback (_app_button);
			}

			if (is_shown (_shortcuts)) {
				callback (_shortcuts);
			}

			foreach (var page in _pages) {
				callback (page.label);
			}

			if (this.current_page != null) {
				callback (this.current_page.page);
			}
		}

		private bool is_shown (Widget? widget) {
			return (widget != null) && widget.visible;
		}

		protected override void size_request (out Requisition requisition) {
			base.size_request (out requisition);

			double tabs_width = 0;
			double tabs_height = 0;
			foreach (var page in _pages) {
				Requisition req;
				page.label.size_request (out req);
				tabs_width += req.width;
				tabs_height = Math.fmax (tabs_height, req.height);
				page.label_requisition = req;
			}
			tabs_width += _pages.size * 2 * TAB_PADDING;
			tabs_height += 2 * TAB_PADDING;

			double header_width = tabs_width;

			if (is_shown (_shortcuts)) {
				_shortcuts.size_request (out _shortcuts_requisition);
				double x = _shortcuts_requisition.width + SPACE;
				header_width += Math.fmax (x, TABS_MIN_HPOS);
			} else {
				_shortcuts_requisition = Requisition ();
				header_width += TABS_MIN_HPOS;
			}

			_header_height = Math.fmax (tabs_height, _shortcuts_requisition.height);

			if (is_shown (_toolbar)) {
				_toolbar.size_request (out _toolbar_requisition);
			}
			if (is_shown (_app_button)) {
				_app_button.size_request (out _app_button_requisition);
			}

			if (is_shown (_toolbar)) {
				_header_height += 2 * SPACE + _toolbar_requisition.height;
			}

			if (is_shown (_app_button)) {
				_header_height = Math.fmax (_header_height, SPACE + _app_button_requisition.height);
			}

			double page_width = 0;
			double page_height = 0;
			if (this.current_page != null) {
				this.current_page.page.size_request (out _page_requisition);
				page_width = _page_requisition.width + 2 * PAGE_PADDING;
				page_height = _page_requisition.height + 2 * PAGE_PADDING;
			} else {
				_page_requisition = Requisition ();
			}

			double width = Math.fmax (header_width, page_width);
			width = BORDER_WIDTH + width + BORDER_WIDTH;
			double height = _header_height + page_height + 2 * BORDER_WIDTH;

			requisition.width = (int) Math.ceil (width - double.EPSILON);
			requisition.height = (int) Math.ceil (height - double.EPSILON);
		}

		protected override void size_allocate (Rectangle allocation) {
			base.size_allocate (allocation);

			if (allocation.height < _header_height + BORDER_WIDTH) {
				return;
			}

			double header_bottom = allocation.y + BORDER_WIDTH + _header_height;
			double current_x = BORDER_WIDTH;

			if (is_shown (_app_button)) {
				var alloc = Rectangle () {
					x = (int) current_x,
					y = (int) (allocation.y + BORDER_WIDTH),
					width = int.min (_app_button_requisition.width,
					                 (int) (allocation.width - 2 * SPACE)),
					height = _app_button_requisition.height
				};
				_app_button.size_allocate (alloc);

				current_x += alloc.width + SPACE;
			}

			if (is_shown (_toolbar)) {
				Rectangle alloc = Rectangle ();
				alloc.x = (int) current_x;
				alloc.y = (int) (allocation.y + SPACE);
				alloc.width = int.min (_toolbar_requisition.width,
				                       (int) (allocation.width - (alloc.x - allocation.x) - SPACE));
				alloc.height = _toolbar_requisition.height;
				_toolbar.size_allocate (alloc);
			}

			if (is_shown (_shortcuts)) {
				var alloc = Rectangle () {
					x = (int) current_x,
					y = (int) (header_bottom - _shortcuts_requisition.height),
					width = _shortcuts_requisition.width,
					height = _shortcuts_requisition.height
				};
				_shortcuts.size_allocate (alloc);
				current_x += _shortcuts_requisition.width;
			}

			current_x += SPACE;
			current_x = Math.fmax (current_x, TABS_MIN_HPOS);

			foreach (var page in _pages) {
				var alloc = Rectangle () {
					x = (int) (current_x + TAB_PADDING),
					y = (int) (header_bottom - TAB_PADDING - page.label_requisition.height),
					width = page.label_requisition.width,
					height = page.label_requisition.height
				};
				page.label.size_allocate (alloc);

				alloc.x = (int) current_x;
				alloc.y = (int) (header_bottom - page.label_requisition.height - 2 * TAB_PADDING);
				alloc.width = (int) (page.label_requisition.width + 2 * TAB_PADDING);
				alloc.height = (int) (page.label_requisition.height + 2 * TAB_PADDING);
				page.label_allocation = alloc;

				current_x += page.label_requisition.width + 2 * TAB_PADDING;
			}

			_body_allocation.x = allocation.x + (int) BORDER_WIDTH;
			_body_allocation.y = (int) header_bottom;
			_body_allocation.width = allocation.width - _body_allocation.x - (int) BORDER_WIDTH;
			_body_allocation.height = allocation.height - _body_allocation.y - (int) BORDER_WIDTH;

			if (this.current_page != null) {
				_page_allocation = _body_allocation;
				int pad = (int) PAGE_PADDING;
				Rect.inflate (ref _page_allocation, -pad, -pad);
				this.current_page.page.size_allocate (_page_allocation);
			} else {
				_page_allocation = Rectangle ();
			}
		}

		protected override bool expose_event (EventExpose event) {
			var cr = Gdk.cairo_create (this.window);

			cr.rectangle (event.area.x, event.area.y,
			              event.area.width, event.area.height);
			cr.clip ();
			draw (cr);

//			cr.target.dispose ();	// XXX-GEETK: are these necessary with Vala?
//			cr.dispose ();

			return base.expose_event (event);
		}

		protected void draw (Context cr) {
			var menu_bar_allocation = Rectangle () {
				x = this.allocation.x,
				y = this.allocation.y,
				width = this.allocation.width
			};
			if (is_shown (_toolbar)) {
				menu_bar_allocation.height = (int) (_toolbar.allocation.height + 2 * SPACE);
			} else {
				menu_bar_allocation.height = 0;
			}  	 
			_theme.draw_ribbon (cr, menu_bar_allocation, _body_allocation,
									ROUND_SIZE, LINE_WIDTH, this);
		}

		internal override void remove (Widget widget) {
			// XXX-GEETK
		}
	}


	/** Ribbon page. */
	public class RibbonPage : GLib.Object {

		public Ribbon parent { private get; construct; }

		/** Widget used as the content of the page. */
		public Widget page { get; set; }

		public Requisition label_requisition { get; set; }
		public Rectangle label_allocation { get; set; }

		/** Label widget of the page. */
		private Widget _label;
		public Widget label {
			set {
				if (_label != null) {
					_label.unparent ();
				}
				_label = value;
				if (_label != null) {
					_label.set_parent (this.parent);
				}
			}
			get { return _label; }
		}

		public RibbonPage (Ribbon parent, Widget page, Widget label) {
			this.parent = parent;
			this.page = page;
			this.label = label;
		}
	}
}

